'use strict';
const fs = require('fs')
const path = require('path')
const ejs = require('ejs')
const glob = require('glob')
const userHome = require('user-home')
const inquirer = require('inquirer')
const fse = require('fs-extra')
const semver = require('semver')
const Command = require('@tangmen-cli-dev/command')
const Package = require('@tangmen-cli-dev/package')
const log = require('@tangmen-cli-dev/log')
const { spinnerStart, sleep, execAsync } = require('@tangmen-cli-dev/utils')

const getProjectTemplate = require('./getProjectTemplate')


const TYPE_PROJECT = 'project'
const TYPE_COMPONENT = 'component'
const TEMPLATE_TYPE_NORMAL = 'normal'
const TEMPLATE_TYPE_CUSTOM = 'custom'
const WHITE_LIST = ['npm', 'cnpm']

class InitCommand extends Command {
    /**
     * 初始化函数，拿到项目名称和是否强制更新的参数
     */
    init() {
        this.projectName = this._argv[0] || ''
        this.force = !!this._cmd.force
        log.verbose('projectName', this.projectName)
        log.verbose('force', this.force)
    }

    /**
     * 执行函数
     */
    async exec() {
        try {
            // 1.准备阶段
            const projectInfo = await this.prepare()
            if (projectInfo) {
                // 2.下载模板
                this.projectInfo = projectInfo
                await this.downloadTemplate()
                    // 3.安装模板
                await this.installTemplate()
            }
        } catch (e) {
            log.error(e.message)
        }
        // 下载模板
        // 安装模板
    }

    /**
     * 准备阶段
     */
    async prepare() {
        const template = await getProjectTemplate()
        if (!template || template.length === 0) {
            throw new Error('项目模板不存在')
        }
        this.template = template
        const localPath = process.cwd()
            // 判断当前目录是否为空
        if (!this.isDirEmpty(localPath)) {
            // 询问是否继续创建 使用到inquirer这个库
            // 如果 用户不是强制更新，那么就要询问用户是否继续创建
            let ifContinue = false
            if (!this.force) {
                ifContinue = (await inquirer.prompt({
                    type: 'confirm',
                    name: 'ifContinue',
                    message: '当前目录不为空，是否继续创建？',
                    default: false,
                })).ifContinue
                if (!ifContinue) {
                    return
                }
            }
            // 不管用户是否是强制更新，最后都会展示这次询问，因为清空当前目录文件是一个非常严谨的操作
            if (ifContinue || this.force) {
                // 做二次确认
                const { confirmDelete } = await inquirer.prompt({
                    type: 'confirm',
                    name: 'confirmDelete',
                    message: '是否确认清空当前目录下的文件？',
                    default: false,
                })
                if (confirmDelete) {
                    // 清空当前目录 使用 fse-extra
                    fse.emptyDirSync(localPath)
                }

            }
        }

        // 获取项目的基本信息
        return this.getProjectInfo()
    }

    // 获取项目信息，使用inquirer这个库来获取用户输入，说白了，项目的信息还是要由使用方来定义
    // 创建项目或者组件
    async getProjectInfo() {
        // 对用户输入的项目名称进行校验
        function isValidName(v) {
            return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v);
        }
        let projectInfo = {}
        let isProjectNameValid = false
            // 如果用户输入的名称符合我们的规则 就直接用这个
        if (isValidName(this.projectName)) {
            isProjectNameValid = true
            projectInfo.projectName = this.projectName
        }
        const { type } = await inquirer.prompt({
                type: 'list',
                name: 'type',
                message: '请选择初始化项目类型?',
                default: TYPE_PROJECT,
                choices: [{
                    name: '项目',
                    value: TYPE_PROJECT
                }, {
                    name: '组件',
                    value: TYPE_COMPONENT
                }]
            })
            // 打印一下类型看看是组件还是项目
        log.verbose('type', type)
            // 找到对应的模板数组
        this.template = this.template.filter(template => {
            return template.tag.includes(type)
        })
        const title = type === TYPE_PROJECT ? '项目' : '组件'
        const projectNamePrompt = {
            type: 'input',
            name: 'projectName',
            message: `请输入${title}名称`,
            default: '',
            validate: function(v) {
                const done = this.async()
                    // 1.首字符必须为英文字符
                    // 2.尾字符必须为英文字符或数字，不能为字符
                    // 3.字符仅允许“-_”
                    // 4.兼容只有一个字母的情况
                setTimeout(function() {
                    if (!isValidName(v)) {
                        done(`请输入合法的${title}名称,例：a1 | a_b_c | a1_b1_c1`)
                        return
                    }
                    // Pass the return value in the done callback
                    done(null, true)
                }, 0)
            },
            filter: function(v) {
                return v
            }
        }
        const projectPrompt = []
        if (!isProjectNameValid) {
            projectPrompt.push(projectNamePrompt)
        }
        projectPrompt.push({
            type: 'input',
            name: 'projectVersion',
            message: `请输入${title}版本号`,
            default: '1.0.0',
            validate: function(v) {
                const done = this.async()

                setTimeout(function() {
                    if (!(!!semver.valid(v))) {
                        done('请输入合法的项目版本号,例：1.0.0')
                        return
                    }
                    // Pass the return value in the done callback
                    done(null, true)
                }, 0)
                return
            },
            filter: function(v) {
                if (!!semver.valid(v)) {
                    return semver.valid(v)
                }
                return v
            }
        }, {
            type: 'list',
            name: 'projectTemplate',
            message: `请选择${title}模板`,
            choices: this.createProjectTemplate()
        })
        if (type === TYPE_PROJECT) {
            const project = await inquirer.prompt(projectPrompt)
            projectInfo = {
                ...projectInfo,
                type,
                ...project
            }
        } else if (type === TYPE_COMPONENT) {
            const descriptionPrompt = {
                type: 'input',
                name: 'componentDescription',
                message: '请输入组件描述信息',
                default: '',
                validate: function(v) {
                    const done = this.async()
                    setTimeout(() => {
                        if (!v) {
                            done('请输入组件描述信息')
                            return
                        }
                        done(null, true)
                    }, 0);

                }
            }
            projectPrompt.push(descriptionPrompt)
            const component = await inquirer.prompt(projectPrompt)
            projectInfo = {
                ...projectInfo,
                type,
                ...component
            }
        }
        // 生成classname
        if (projectInfo.projectName) {
            projectInfo.name = projectInfo.projectName
            projectInfo.className = require('kebab-case')(projectInfo.projectName)
        }
        // 生成version
        if (projectInfo.projectVersion) {
            projectInfo.version = projectInfo.projectVersion
        }
        // 生成description
        if (projectInfo.componentDescription) {
            projectInfo.description = projectInfo.componentDescription
        }
        return projectInfo
    }

    isDirEmpty(localPath) {
        let fileList = fs.readdirSync(localPath)
            // 文件过滤的逻辑
        fileList = fileList.filter(file => (!file.startsWith('.') && ['node_modules'].indexOf(file) < 0))
        return !fileList || fileList.length <= 0
    }

    // 生成项目模板 inquirer的list需要什么，我们就给它什么
    createProjectTemplate() {
        return this.template.map((item) => {
            return {
                name: item.name,
                value: item.npmName
            }
        })
    }

    // 下载模板
    // 通过项目模板API获取项目模板信息
    // 通过egg.js搭建一套后端系统
    // 通过npm存储项目模板
    // 将项目模板信息存储到mongodb数据库中
    // 通过egg.js获取mongodb中的数据模板信息并且通过API返回
    async downloadTemplate() {
        const { projectTemplate } = this.projectInfo
        const templateInfo = this.template.find((item) => item.npmName === projectTemplate)
        const targetPath = path.resolve(userHome, '.tangmen-cli-dev', 'template')
        const storeDir = path.resolve(userHome, '.tangmen-cli-dev', 'template', 'node_modules')
        const { npmName, version } = templateInfo
        this.templateInfo = templateInfo
        const templateNpm = new Package({
            targetPath,
            storeDir,
            packageName: npmName,
            packageVersion: version
        })
        if (!await templateNpm.exists()) {
            const spinner = spinnerStart('正在下载模板...')
            await sleep()
            try {
                await templateNpm.install()
            } catch (e) {
                throw e
            } finally {
                spinner.stop(true)
                if (await templateNpm.exists()) {
                    log.success('下载模板成功')
                }
                this.templateNpm = templateNpm
            }
        } else {
            const spinner = spinnerStart('正在更新模板...')
            await sleep()
            try {
                await templateNpm.update()
            } catch (e) {
                throw e
            } finally {
                spinner.stop(true)
                if (await templateNpm.exists()) {
                    log.success('更新模板成功')
                }
                this.templateNpm = templateNpm
            }
        }

    }

    // 对cmd命令进行一次检验，只有在白名单里的才能通过检验，否则返回null
    checkCommand(cmd) {
        if (WHITE_LIST.includes(cmd)) {
            return cmd
        }
        return null
    }



    // 安装模板
    async installTemplate() {
        if (this.templateInfo) {
            if (!this.templateInfo.type) {
                // 默认为标准模板类型
                this.templateInfo.type = TEMPLATE_TYPE_NORMAL
            }
            if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
                this.installNormalTemplate()
            } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
                this.installCustomTemplate()
            } else {
                throw new Error('无法识别项目模板类型')
            }
        } else {
            throw new Error('项目模板信息不存在')
        }
    }

    /**
     * 执行脚本函数
     * @param {*} command 脚本命令
     * @param {*} errMsg 错误提示语
     */
    async execCommand(command, errMsg) {
        let ret;
        if (command) {
            const cmdArray = command.split(' ')
            const cmd = this.checkCommand(cmdArray[0])
            if (!cmd) {
                throw new Error('命令不存在! 命令: ' + command)
            }
            const args = cmdArray.slice(1)
            ret = await execAsync(cmd, args, {
                stdio: 'inherit',
                cwd: process.cwd()
            })
        }
        if (ret !== 0) {
            throw new Error(errMsg)
        }
        return ret
    }

    // ejs渲染函数
    async ejsRender(options) {
        const dir = process.cwd()
        const projectInfo = this.projectInfo
        return new Promise((resolve, reject) => {
            glob('**', {
                cwd: dir,
                ignore: options.ignore || '',
                nodir: true
            }, function(err, files) {
                if (err) {
                    reject(err)
                }
                Promise.all(files.map((file) => {
                    const filePath = path.join(dir, file)
                    return new Promise((resolve1, reject1) => {
                        ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
                            if (err) {
                                reject1(err)
                            } else {
                                fse.writeFileSync(filePath, result)
                                resolve1(result)
                            }
                        })
                    })
                })).then(() => {
                    resolve()
                }).catch((err) => {
                    reject(err)
                })
            })
        })
    }

    // 安装标准模板
    async installNormalTemplate() {
        // 从用户主目录的缓存路径里拿到项目模板，拷贝到指定的文件夹下
        const spinner = spinnerStart('正在安装模板...')
        await sleep()
        try {
            const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template')
            const targetPath = process.cwd()
            fse.ensureDirSync(templatePath)
            fse.ensureDirSync(targetPath)
            fse.copySync(templatePath, targetPath)
        } catch (e) {
            throw e
        } finally {
            spinner.stop(true)
            log.success('标准模板安装成功')
        }
        const templateIgnore = this.templateInfo.ignore || []
        const ignore = ['**/node_modules/**', ...templateIgnore]
        await this.ejsRender({ ignore })
            // 模板拷贝完了以后要进行依赖安装
            // 从数据库拿到安装的脚本命令 npm ['install']
        const { installCommand, startCommand } = this.templateInfo
            // 依赖安装
        await this.execCommand(installCommand, '依赖安装过程出现错误！')
            // 启动命令
        await this.execCommand(startCommand, '模板启动过程中出现错误!')


    }
    async installCustomTemplate() {
        // 查询自定义模板的入口文件
        if (await this.templateNpm.exists()) {
            const rootFile = await this.templateNpm.getRootFilePath();
            if (fs.existsSync(rootFile)) {
                log.notice('开始执行自定义模板');
                const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template');
                const options = {
                    templateInfo: this.templateInfo,
                    projectInfo: this.projectInfo,
                    sourcePath: templatePath,
                    targetPath: process.cwd(),
                };
                const code = `require('${rootFile}')(${JSON.stringify(options)})`;
                log.verbose('code', code);
                await execAsync('node', ['-e', code], { stdio: 'inherit', cwd: process.cwd() });
                log.success('自定义模板安装成功');
            } else {
                throw new Error('自定义模板入口文件不存在！');
            }
        }
    }


}


function init(argv) {
    return new InitCommand(argv)
}

module.exports = init;
module.exports.InitCommand = InitCommand;